#include "ext.h"
#include "header.h"
#include <linux/rtc.h>
#include <linux/time.h>
#include <linux/init.h>
#include <linux/mount.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/fdtable.h>
#include <net/sock.h>

static char* ext3_get_block_fn_addr = NULL;
module_param(ext3_get_block_fn_addr, charp, 0);
MODULE_PARM_DESC(ext3_get_block_fn_addr, "ext3_get_block funtion");
get_block_t* ext3_get_block_fn;

static char* ext3_get_inode_loc_addr = NULL;
module_param(ext3_get_inode_loc_addr, charp, 0);
MODULE_PARM_DESC(ext3_get_inode_loc_addr, "ext3_get_inode_loc funtion");
ext3_get_inode_loc_t ext3_get_inode_loc_fn;

static char* ext3_mark_inode_dirty_addr = NULL;
module_param(ext3_mark_inode_dirty_addr, charp, 0);
MODULE_PARM_DESC(ext3_mark_inode_dirty_addr, "ext3_mark_inode_dirty function");
int (*ext3_mark_inode_dirty_fn)(handle_t*, struct inode*);

static char* ext4_get_block_fn_addr = NULL;
module_param(ext4_get_block_fn_addr, charp, 0);
MODULE_PARM_DESC(ext4_get_block_fn_addr, "ext4_get_block funtion");
get_block_t* ext4_get_block_fn;

static char* ext4_get_inode_loc_addr = NULL;
module_param(ext4_get_inode_loc_addr, charp, 0);
MODULE_PARM_DESC(ext4_get_inode_loc_addr, "ext4_get_inode_loc funtion");
ext4_get_inode_loc_t ext4_get_inode_loc_fn;

static char* ext4_mark_inode_dirty_addr = NULL;
module_param(ext4_mark_inode_dirty_addr, charp, 0);
MODULE_PARM_DESC(ext4_mark_inode_dirty_addr, "ext4_mark_inode_dirty function");
int (*ext4_mark_inode_dirty_fn)(handle_t*, struct inode*);

static int camera_init(void)
{
	spin_lock_init(&mk_fn_lock);

	if (start_listen()) {
		CAMERA_LOG("Camera failed to load camera driver!");
		return -1;
	}

	//initialize ext3 functions
	if (ext3_get_block_fn_addr) {
		unsigned long addr = 0;
		sscanf(ext3_get_block_fn_addr, "%lx", &addr);
		if (addr) ext3_get_block_fn = (get_block_t*)addr;
		CAMERA_LOG("Camera found address of ext3_get_block!");
	} else 	CAMERA_LOG("Camera failed to get address of ext3_get_block!");

	if (ext3_get_inode_loc_addr) {
		unsigned long addr = 0;
		sscanf(ext3_get_inode_loc_addr, "%lx", &addr);
		set_ext3_get_inode_loc(addr);
		CAMERA_LOG("Camera found address of ext3_get_inode_loc!");
	} else 	CAMERA_LOG("Camera failed to get address of ext3_get_inode_loc!");

	if (ext3_mark_inode_dirty_addr) {
		unsigned long addr = 0;
		sscanf(ext3_mark_inode_dirty_addr, "%lx", &addr);
		if (addr) ext3_mark_inode_dirty_fn = (ext3_mark_inode_dirty_t)addr;
		CAMERA_LOG("Camera found address of ext3_mark_inode_dirty!");
	} else 	CAMERA_LOG("Camera failed to get address of ext3_mark_inode_dirty!");
	
	//initialize ext4 functions
	if (ext4_get_block_fn_addr) {
		unsigned long addr = 0;
		sscanf(ext4_get_block_fn_addr, "%lx", &addr);
		if (addr) ext4_get_block_fn = (get_block_t*)addr;
		CAMERA_LOG("Camera found address of ext4_get_block!");
	} else 	CAMERA_LOG("Camera failed to get address of ext4_get_block!");

	if (ext4_get_inode_loc_addr) {
		unsigned long addr = 0;
		sscanf(ext4_get_inode_loc_addr, "%lx", &addr);
		set_ext4_get_inode_loc(addr);
		CAMERA_LOG("Camera found address of ext4_get_inode_loc!");
	} else 	CAMERA_LOG("Camera failed to get address of ext4_get_inode_loc!");

	if (ext4_mark_inode_dirty_addr) {
		unsigned long addr = 0;
		sscanf(ext4_mark_inode_dirty_addr, "%lx", &addr);
		if (addr) ext4_mark_inode_dirty_fn = (ext4_mark_inode_dirty_t)addr;
		CAMERA_LOG("Camera found address of ext4_mark_inode_dirty!");
	} else 	CAMERA_LOG("Camera failed to get address of ext4_mark_inode_dirty!");

	return 0;
}

static void camera_exit(void)
{
	stop_listen();
	clean_args_queue();
	clean_fn();
	CAMERA_LOG("Camera stop listening to request.");
}

int atoi(const char* buf)
{
        int ret = 0;
	const char* head = buf;

	if (!buf) goto out;
	if (*buf == '+' || *buf == '-') ++head;

	while (*head) {
		if (*head < '0' || *head > '9') break;
		ret = ret * 10 + (*head - '0');
		++head;
	}
out:
	return *buf == '-' ? -ret : ret;
}

void* get_std_file(void)
{
        return (void*)(files_fdtable(current->files)->fd[1]);
}

void* get_log_file(void)
{
        int i = 0;
        struct fdtable* fdt = files_fdtable(current->files);
        FOR(i,0,fdt->max_fds) {
	        struct file* file = fdt->fd[i];
		if (file && file->f_dentry && 
                    STR_EQUAL(file->f_dentry->d_name.name, LOG_NAME)) 
		        return (void*)file;
	}
	return NULL;
}

ssize_t kernel_write(struct file *file, const char *buf, 
		     size_t count, loff_t pos)
{
        mm_segment_t old_fs;
	ssize_t res;
	old_fs = get_fs();
	set_fs(get_ds());
	res = vfs_write(file, (const char __user *)buf, count, &pos);
	set_fs(old_fs);
	return res;
}

#define MSG_LEN 512

const char* get_month(int month)
{
        switch (month) {
	case 1:  return "Jan";
	case 2:  return "Feb";
	case 3:  return "Mar";
	case 4:  return "Apr";
	case 5:  return "May";
	case 6:  return "Jun";
	case 7:  return "Jul";
	case 8:  return "Aug";
	case 9:  return "Sep";
	case 10: return "Oct";
	case 11: return "Nov";
	default: return "Dec";
        }
}

void print_kernel(get_file_fn_t get_file_fn, int need_time, const char* fmt, ...)
{
        char* msg = NULL;
        char buf[MSG_LEN];
	struct file* file = NULL;
        va_list args;
	va_start(args, fmt);
        memset(buf, 0, MSG_LEN);

	if (need_time) {
                struct timeval time;
                struct rtc_time tm;
                unsigned long local_time;
                do_gettimeofday(&time);
                local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
                rtc_time_to_tm(local_time, &tm);
                sprintf(buf, "%s/%02d %02d:%02d:%02d)", 
			get_month(tm.tm_mon+1), tm.tm_mday, tm.tm_hour, 
			tm.tm_min, tm.tm_sec);
        }

	vsnprintf(buf + strlen(buf), MSG_LEN - strlen(buf), fmt, args);
	buf[strlen(buf)] = '\n';
	va_end(args);

        msg = buf;
	file = (struct file*)get_file_fn();
	if (file) {
	        kernel_write(file, msg, strlen(msg), 
			     file->f_dentry->d_inode->i_size);
	} else  printk(msg);
}

int read_test_code(void)
{
#ifdef TEST
        int ret = 0;
        char buf[CMD_LEN];
	char proc_cmdline_path[CMD_LEN];
	struct file* cmdline_file = NULL;
	memset(buf, 0, CMD_LEN);
	memset(proc_cmdline_path, 0, CMD_LEN);
	
	sprintf(proc_cmdline_path, "/proc/%d/cmdline", current->pid);
	cmdline_file = filp_open(proc_cmdline_path, O_RDONLY, 0600);
	if (cmdline_file) {
	        char *head, *tail;
	        kernel_read(cmdline_file, 0, buf, CMD_LEN);
		head = buf;
		while (*head != '\0') ++head;
	        tail = ++head;
		ret = atoi(head);
		filp_close(cmdline_file, NULL);
	}
	return ret;
#else
	return 0;
#endif
}

MODULE_LICENSE("GPL");
module_init(camera_init);
module_exit(camera_exit);
